<?php
/**
 * PDO_MYSQL数据库封装
 * @author Shuky
 */
class Db
{
	const DEFAULT_HOST = 'localhost';
	const DEFAULT_CHARSET = 'utf8';

    const INT_TYPE    = 0;
    const BIGINT_TYPE = 1;
    const FLOAT_TYPE  = 2;
	
	protected $_type = 'mysql';

    protected $_connection = null;

	protected $_configs = array();

	protected $_fetchMode = PDO::FETCH_ASSOC;

	protected $_numericDataTypes = array(
		self::INT_TYPE => self::INT_TYPE,
		self::BIGINT_TYPE => self::BIGINT_TYPE,
		self::FLOAT_TYPE => self::FLOAT_TYPE
	);

	public function __construct($configs)
	{
		$this->_configs = $configs;
	}
    

	public function _connect()
	{
		$configs = $this->_configs;

		if (null === $this->_connection) {
			$options = array(
				PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES '". (!empty($configs['charset']) ? $configs['charset'] : self::DEFAULT_CHARSET) ."'"
			);
			if (isset($configs['persistent']) && $configs['persistent'] == true) {
				$options[PDO::ATTR_PERSISTENT] = true;
			}
			$dbname = isset($configs['dbname']) ? 'dbname='. $configs['dbname'] .';' : '';
			$host = 'host='. (isset($configs['host']) ? $configs['host'] : self::DEFAULT_HOST);
			$dsn = $this->_type .':'. $dbname . $host;
			$this->_connection = new PDO($dsn, $configs['username'], $configs['password'], $options);
		}
	}

	
	/**
	 * Query
	 */
    public function query($sql, $bind = array())
    {
        $this->_connect();
        if (!is_array($bind)) {
            $bind = array($bind);
        }
		$stmt = $this->_connection->prepare($sql);
        $stmt->execute($bind);
        $stmt->setFetchMode($this->_fetchMode);
        return $stmt;
    }


	public function __call($name, array $arguments)
	{
		return call_user_func_array(array($this->_connection, $name), $arguments);
	}


    /**
     * 插入数据
     */
    public function insert($table, array $bind)
    {
        $cols = array();
        $vals = array();
        foreach ($bind as $col => $val) {
            $cols[] = $this->quoteIdentifier($col, true);
			$vals[] = '?';
        }

        // build the statement
        $sql = "INSERT INTO "
             . $this->quoteIdentifier($table, true)
             . ' (' . implode(', ', $cols) . ') '
             . 'VALUES (' . implode(', ', $vals) . ')';

        // execute the statement and return the number of affected rows
        $stmt = $this->query($sql, array_values($bind));
        $result = $stmt->rowCount();
        return $result;
    }


    /**
     * 更新数据库
     */
    public function update($table, array $bind, $where = '')
    {
		// Build "col = ?" pairs for the statement
        $set = array();
        $i = 0;
        foreach ($bind as $col => $val) {
			$val = '?';
            $set[] = $this->quoteIdentifier($col, true) . ' = ' . $val;
        }

        $where = $this->_whereExpr($where);

		// Build the UPDATE statement
        $sql = 'UPDATE '. $this->quoteIdentifier($table, true) .' SET '. implode(', ', $set) . (($where) ? " WHERE $where" : '');

		// Execute the statement and return the number of affected rows
		$stmt = $this->query($sql, array_values($bind));
        $result = $stmt->rowCount();
        return $result;
    }


    /**
	 * 删除
     */
    public function delete($table, $where = '')
    {
        $where = $this->_whereExpr($where);

		// Build the DELETE statement
        $sql = 'DELETE FROM '. $this->quoteIdentifier($table, true) . (($where) ? " WHERE $where" : '');

		// Execute the statement and return the number of affected rows
        $stmt = $this->query($sql);
        $result = $stmt->rowCount();
        return $result;
    }


	/**
	 * 取得所有
	 */
    public function fetchAll($sql, $bind = array(), $fetchMode = null)
    {
        if ($fetchMode === null) {
            $fetchMode = $this->_fetchMode;
        }
        $stmt = $this->query($sql, $bind);
        $result = $stmt->fetchAll($fetchMode);
        return $result;
    }


	/**
	 * 取得一行
	 */
    public function fetchRow($sql, $bind = array(), $fetchMode = null)
    {
        if ($fetchMode === null) {
            $fetchMode = $this->_fetchMode;
        }
        $stmt = $this->query($sql, $bind);
        $result = $stmt->fetch($fetchMode);
        return $result;
    }


	/**
	 * 取得一列
	 */
    public function fetchCol($sql, $bind = array())
    {
        $stmt = $this->query($sql, $bind);
        $result = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
        return $result;
    }


	/**
	 * 捉对获取
	 */
    public function fetchPairs($sql, $bind = array())
    {
        $stmt = $this->query($sql, $bind);
        $data = array();
        while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
            $data[$row[0]] = $row[1];
        }
        return $data;
    }


	/**
	 * 取得一个
	 */
    public function fetchOne($sql, $bind = array())
    {
        $stmt = $this->query($sql, $bind);
        $result = $stmt->fetchColumn(0);
        return $result;
    }
	
	
    /**
     * Safely quotes a value for an SQL statement.
     */
    public function quote($value, $type = null)
    {
        $this->_connect();

        if (is_array($value)) {
            foreach ($value as &$val) {
                $val = $this->quote($val, $type);
            }
            return implode(', ', $value);
        }

        if ($type !== null && array_key_exists($type = strtoupper($type), $this->_numericDataTypes)) {
            $quotedValue = '0';
            switch ($this->_numericDataTypes[$type]) {
                case self::INT_TYPE: // 32-bit integer
                    $quotedValue = (string) intval($value);
                    break;
                case self::BIGINT_TYPE: // 64-bit integer
                    // ANSI SQL-style hex literals (e.g. x'[\dA-F]+')
                    // are not supported here, because these are string
                    // literals, not numeric literals.
                    if (preg_match('/^(
                          [+-]?                  # optional sign
                          (?:
                            0[Xx][\da-fA-F]+     # ODBC-style hexadecimal
                            |\d+                 # decimal or octal, or MySQL ZEROFILL decimal
                            (?:[eE][+-]?\d+)?    # optional exponent on decimals or octals
                          )
                        )/x',
                        (string) $value, $matches)) {
                        $quotedValue = $matches[1];
                    }
                    break;
                case self::FLOAT_TYPE: // float or decimal
                    $quotedValue = sprintf('%F', $value);
            }
            return $quotedValue;
        }

        return $this->_quote($value);
    }


    /**
     * Quote a raw string.
     */
    protected function _quote($value)
    {
        if (is_int($value)) {
            return $value;
        } elseif (is_float($value)) {
            return sprintf('%F', $value);
        }
        return "'" . addcslashes($value, "\000\n\r\\'\"\032") . "'";
    }



    /**
	 * 解析条件
     */
    protected function _whereExpr($where)
    {
        if (empty($where)) {
            return $where;
        }
        if (!is_array($where)) {
            $where = array($where);
        }
        foreach ($where as $cond => &$term) {
            // is $cond an int? (i.e. Not a condition)
			if (!is_int($cond)) {
				$term = $this->quoteInto($cond, $term);
			}
            $term = '(' . $term . ')';
        }

        $where = implode(' AND ', $where);
        return $where;
    }


    /**
     * Quotes a value and places into a piece of text at a placeholder.
     *
     * The placeholder is a question-mark; all placeholders will be replaced
     * with the quoted value.   For example:
     *
     * <code>
     * $text = "WHERE date < ?";
     * $date = "2005-01-02";
     * $safe = $sql->quoteInto($text, $date);
     * // $safe = "WHERE date < '2005-01-02'"
     * </code>
     *
     * @param string  $text  The text with a placeholder.
     * @param mixed   $value The value to quote.
     * @param string  $type  OPTIONAL SQL datatype
     * @param integer $count OPTIONAL count of placeholders to replace
     * @return string An SQL-safe quoted value placed into the original text.
     */
    public function quoteInto($text, $value, $type = null, $count = null)
    {
        if ($count === null) {
            return str_replace('?', $this->quote($value, $type), $text);
        } else {
            while ($count > 0) {
                if (strpos($text, '?') != false) {
                    $text = substr_replace($text, $this->quote($value, $type), strpos($text, '?'), 1);
                }
                --$count;
            }
            return $text;
        }
    }



    public function quoteIdentifier($ident, $auto=false)
    {
        return $this->_quoteIdentifierAs($ident, null, $auto);
    }

    public function quoteColumnAs($ident, $alias, $auto=false)
    {
        return $this->_quoteIdentifierAs($ident, $alias, $auto);
    }

    public function quoteTableAs($ident, $alias = null, $auto = false)
    {
        return $this->_quoteIdentifierAs($ident, $alias, $auto);
    }


    /**
     * Quote an identifier and an optional alias.
     *
     * @param string|array $ident The identifier or expression.
     * @param string $alias An optional alias.
     * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
     * @param string $as The string to add between the identifier/expression and the alias.
     * @return string The quoted identifier and alias.
     */
    protected function _quoteIdentifierAs($ident, $alias = null, $auto = false, $as = ' AS ')
    {
		if (is_string($ident)) {
			$ident = explode('.', $ident);
		}
		if (is_array($ident)) {
			$segments = array();
			foreach ($ident as $segment) {
				$segments[] = $this->_quoteIdentifier($segment, $auto);
			}
			if ($alias !== null && end($ident) == $alias) {
				$alias = null;
			}
			$quoted = implode('.', $segments);
		} else {
			$quoted = $this->_quoteIdentifier($ident, $auto);
		}
        if ($alias !== null) {
            $quoted .= $as . $this->_quoteIdentifier($alias, $auto);
        }
        return $quoted;
    }


    /**
     * Quote an identifier.
     *
     * @param  string $value The identifier or expression.
     * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
     * @return string        The quoted identifier and alias.
     */
    protected function _quoteIdentifier($value, $auto=false)
    {
        if ($auto === false) {
            $q = '"';
            return ($q . str_replace("$q", "$q$q", $value) . $q);
        }
        return $value;
    }


	public function closeConnection()
	{
		$this->_connection = null;
	}
}